package com.hzgj.soa.spring.schema;

import com.google.common.collect.Sets;

import com.hzgj.bcl.soa.cicada.pool.ChannelPoolConfig;
import com.hzgj.bcl.soa.config.*;
import com.hzgj.bcl.soa.rpc.server.ServerConfig;
import com.hzgj.bcl.util.lang.StrKit;
import com.hzgj.soa.spring.*;
import com.hzgj.soa.spring.corr.CorrListener;

import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.NamespaceHandlerSupport;
import org.springframework.beans.factory.xml.ParserContext;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.Map;
import java.util.Random;
import java.util.Set;

public class SoaNamespaceHandler extends NamespaceHandlerSupport {

    boolean init = false;
    public static final String DOMAINCONFIGBEAN_NAME = "domainConfigBean";
    public static Set<String> domainNames;
    public static String globalReferenceConfig = null;
    public static String globalServiceConfig = null;

    public static void setBeanProperty(BeanDefinition beanDefinition, Element element) {
        int length = element.getAttributes().getLength();
        for (int i = 0; i < length; i++) {
            Node node = element.getAttributes().item(i);
            String name = node.getNodeName();
            String value = node.getNodeValue();
            beanDefinition.getPropertyValues().add(name, value);
        }
    }

    @Override
    public void init() {
        registerBeanDefinitionParser("registry", new RegistryDefBeanDefinitionParser(RegistryDef.class, true));
        registerBeanDefinitionParser("domain", new DomainBeanDefinitionParser());
        registerBeanDefinitionParser("application", new CommonBeanDefinitionParser(ApplicationConfig.class, true));
        registerBeanDefinitionParser("reference", new ReferenceBeanDefinitionParser(ReferenceBean.class, true));
        registerBeanDefinitionParser("service", new ServiceDefinitionParser(ServiceDef.Attr.class, true));
        registerBeanDefinitionParser("server", new ServerBeanBeanDefinitionParser(ServerDef.class, true));
        registerBeanDefinitionParser("consumer", new CommonBeanDefinitionParser(ConsumerConfig.class, true));
        registerBeanDefinitionParser("pool", new CommonBeanDefinitionParser(ServerConfig.BizThreadPoolConfig.class, true));
        registerBeanDefinitionParser("cpool", new CommonBeanDefinitionParser(ChannelPoolConfig.class, true));
        registerBeanDefinitionParser("monitor", new MonitorBeanDefinitionParser(MonitorBean.class));
        registerBeanDefinitionParser("reference-config", new ConfigDefinitionParser(ReferenceConfig.class, true));
        registerBeanDefinitionParser("service-config", new ConfigDefinitionParser(ServiceConfig.class, true));
    }

    @Override
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        if (!init) {
            registryBean(parserContext, CorrListener.class, "corr#corrListener");
            registryBean(parserContext, RpcDispatcherBean.class, "rpcDispatcherBean");
            registryBean(parserContext, DomainConfigBean.class, DOMAINCONFIGBEAN_NAME);
            init = true;
        }
        return super.parse(element, parserContext);
    }

    private RootBeanDefinition registryBean(ParserContext parserContext, Class<?> beanClass, String beanName) {
        RootBeanDefinition beanDefinition = new RootBeanDefinition();
        beanDefinition.setBeanClass(beanClass);
        beanDefinition.setLazyInit(false);
        if (parserContext.getRegistry().containsBeanDefinition(beanName)) {
            throw new RuntimeException("has contain bean name of " + beanName);
        }
        parserContext.getRegistry().registerBeanDefinition(beanName, beanDefinition);
        return beanDefinition;
    }

    class DomainBeanDefinitionParser implements BeanDefinitionParser {

        @Override
        public BeanDefinition parse(Element element, ParserContext parserContext) {
            String name = element.getAttribute("name");
            if (StrKit.isBlank(name)) {
                throw new RuntimeException("domain must has attr name");
            }

            if (domainNames == null) {
                domainNames = Sets.newHashSet();
            }
            if (domainNames.contains(name)) {
                throw new RuntimeException("has contain domain name of " + name);
            }
            domainNames.add(name);
            BeanDefinition beanDefinition = registryBean(parserContext, Domain.class, name);
            beanDefinition.getPropertyValues().add("name", name);

            NodeList childNodeList = element.getChildNodes();
            int length = childNodeList.getLength();
            for (int i = 0; i < length; i++) {
                Node node = childNodeList.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE) {
                    BeanDefinition _beanDefinition = SoaNamespaceHandler.this.parse((Element) node, parserContext);
                    if (_beanDefinition.getBeanClassName().equals(ReferenceBean.class.getName())) {
                        RuntimeBeanReference value = (RuntimeBeanReference) _beanDefinition.getPropertyValues().getPropertyValue("referenceDef").getValue();
                        parserContext.getRegistry().getBeanDefinition(value.getBeanName()).getPropertyValues().add("domain", name);
                    } else {
                        _beanDefinition.getPropertyValues().add("domain", name);
                        if (_beanDefinition.getBeanClassName().equals(ServiceConfig.class.getName())) {
                            beanDefinition.getPropertyValues().add("serviceConfig", _beanDefinition);
                        } else if (_beanDefinition.getBeanClassName().equals(ReferenceConfig.class.getName())) {
                            beanDefinition.getPropertyValues().add("referenceConfig", _beanDefinition);
                        }
                    }
                }
            }
            return beanDefinition;
        }
    }

    static class CommonBeanDefinitionParser extends AbstractBeanDefinitionParser {

        public CommonBeanDefinitionParser(Class<?> beanClass, boolean required) {
            super(beanClass, required);
        }

        @Override
        void parse0(RootBeanDefinition beanDefinition, Element element, ParserContext parserContext, Class<?> beanClass) {
            SoaNamespaceHandler.setBeanProperty(beanDefinition, element);
        }
    }

    class ServiceDefinitionParser extends CommonBeanDefinitionParser {

        public ServiceDefinitionParser(Class<?> beanClass, boolean required) {
            super(beanClass, required);
        }

        @Override
        void parse0(RootBeanDefinition beanDefinition, Element element, ParserContext parserContext, Class<?> beanClass) {
            super.parse0(beanDefinition, element, parserContext, beanClass);
            Map<String, MethodDef> map = ElementParser.parserMethod(element, beanDefinition);
            beanDefinition.getPropertyValues().addPropertyValue("methodDefs", map);
        }
    }

    class ConfigDefinitionParser extends CommonBeanDefinitionParser {

        public ConfigDefinitionParser(Class<?> beanClass, boolean required) {
            super(beanClass, required);
        }

        @Override
        void parse0(RootBeanDefinition beanDefinition, Element element, ParserContext parserContext, Class<?> beanClass) {
            super.parse0(beanDefinition, element, parserContext, beanClass);
            Node parentNode = element.getParentNode();
            if (!parentNode.getLocalName().equals("domain")) {
                if (beanClass.getName().equals(ReferenceConfig.class.getName())) {
                    if (globalReferenceConfig != null) {
                        throw new RuntimeException("only one reference-config in dom");
                    }
                    globalReferenceConfig = (String) beanDefinition.getPropertyValues().getPropertyValue("id").getValue();
                } else if (beanClass.getName().equals(ServiceConfig.class.getName())) {
                    if (globalServiceConfig != null) {
                        throw new RuntimeException("only one service-config in dom");
                    }
                    globalServiceConfig = (String) beanDefinition.getPropertyValues().getPropertyValue("id").getValue();
                }
            }
        }
    }

    class ReferenceBeanDefinitionParser extends AbstractBeanDefinitionParser {

        private Random random = new Random();

        public ReferenceBeanDefinitionParser(Class<?> beanClass, boolean required) {
            super(beanClass, required);
        }

        @Override
        void parse0(RootBeanDefinition beanDefinition, Element element, ParserContext parserContext, Class<?> beanClass) {
            RootBeanDefinition referenceBeanDefinition = new RootBeanDefinition();
            referenceBeanDefinition.setBeanClass(ReferenceDef.class);
            referenceBeanDefinition.setLazyInit(false);
            SoaNamespaceHandler.setBeanProperty(referenceBeanDefinition, element);
            String id = (String) beanDefinition.getPropertyValues().getPropertyValue("id").getValue();
            referenceBeanDefinition.getPropertyValues().add("id", id);

            String beanName = "referenceDef#" + random.nextDouble();
            while (parserContext.getRegistry().containsBeanDefinition(beanName)) {
                beanName = "referenceDef#" + random.nextDouble();
            }

            Map<String, MethodDef> methodDefs = ElementParser.parserMethod(element, referenceBeanDefinition);
            referenceBeanDefinition.getPropertyValues().addPropertyValue("methodDefs", methodDefs);

            parserContext.getRegistry().registerBeanDefinition(beanName, referenceBeanDefinition);
            beanDefinition.getPropertyValues().addPropertyValue("referenceDef", new RuntimeBeanReference(beanName));
        }
    }
}